home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / DistUpgrade / DistUpgradeCache.py < prev    next >
Encoding:
Python Source  |  2009-04-27  |  44.2 KB  |  1,037 lines

  1. # DistUpgradeCache.py 
  2. #  
  3. #  Copyright (c) 2004-2008 Canonical
  4. #  
  5. #  Author: Michael Vogt <michael.vogt@ubuntu.com>
  6. #  This program is free software; you can redistribute it and/or 
  7. #  modify it under the terms of the GNU General Public License as 
  8. #  published by the Free Software Foundation; either version 2 of the
  9. #  License, or (at your option) any later version.
  10. #  This program is distributed in the hope that it will be useful,
  11. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #  GNU General Public License for more details.
  14. #  You should have received a copy of the GNU General Public License
  15. #  along with this program; if not, write to the Free Software
  16. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  17. #  USA
  18.  
  19. import warnings
  20. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  21. import apt
  22. import apt_pkg
  23. import os
  24. import os.path
  25. import re
  26. import logging
  27. import string
  28. import statvfs
  29. import time
  30. import gettext
  31. import datetime
  32. import threading
  33. import ConfigParser
  34. from subprocess import Popen, PIPE
  35.  
  36. from DistUpgradeGettext import gettext as _
  37. from DistUpgradeGettext import ngettext
  38. from DistUpgradeConfigParser import DistUpgradeConfig
  39. from DistUpgradeView import FuzzyTimeToStr
  40.  
  41. class CacheException(Exception):
  42.     pass
  43. class CacheExceptionLockingFailed(CacheException):
  44.     pass
  45. class CacheExceptionDpkgInterrupted(CacheException):
  46.     pass
  47.  
  48. # the initrd/vmlinuz/abi space required in /boot for each kernel
  49. KERNEL_INITRD_SIZE = 15*1024*1024
  50.  
  51. class FreeSpaceRequired(object):
  52.     """ FreeSpaceRequired object:
  53.     
  54.     This exposes:
  55.     - the total size required (size_total)
  56.     - the dir that requires the space (dir)
  57.     - the additional space that is needed (size_needed)
  58.     """
  59.     def __init__(self, size_total, dir, size_needed):
  60.         self.size_total = size_total
  61.         self.dir = dir
  62.         self.size_needed = size_needed
  63.     def __str__(self):
  64.         return "FreeSpaceRequired Object: Dir: %s size_total: %s size_needed: %s" % (self.dir, self.size_total, self.size_needed)
  65.     
  66.  
  67. class NotEnoughFreeSpaceError(CacheException):
  68.     """ 
  69.     Exception if there is not enough free space for this operation 
  70.     
  71.     """
  72.     def __init__(self, free_space_required_list):
  73.         self.free_space_required_list = free_space_required_list
  74.  
  75. class MyCache(apt.Cache):
  76.     ReInstReq = 1
  77.     HoldReInstReq = 3
  78.  
  79.     # init
  80.     def __init__(self, config, view, quirks, progress=None, lock=True):
  81.         apt.Cache.__init__(self, progress)
  82.         self.to_install = []
  83.         self.to_remove = []
  84.         self.view = view
  85.         self.quirks = quirks
  86.         self.lock = False
  87.         self.partialUpgrade = False
  88.         self.config = config
  89.         self.metapkgs = self.config.getlist("Distro","MetaPkgs")
  90.         # acquire lock
  91.         self._listsLock = -1
  92.         if lock:
  93.             try:
  94.                 apt_pkg.PkgSystemLock()
  95.                 self.lockListsDir()
  96.                 self.lock = True
  97.             except SystemError, e:
  98.                 # checking for this is ok, its not translatable
  99.                 if "dpkg --configure -a" in str(e):
  100.                     raise CacheExceptionDpkgInterrupted, e
  101.                 raise CacheExceptionLockingFailed, e
  102.         # a list of regexp that are not allowed to be removed
  103.         self.removal_blacklist = config.getListFromFile("Distro","RemovalBlacklistFile")
  104.         self.uname = Popen(["uname","-r"],stdout=PIPE).communicate()[0].strip()
  105.         self._initAptLog()
  106.         # from hardy on we use recommends by default, so for the 
  107.         # transition to the new dist we need to enable them now
  108.         if (config.get("Sources","From") == "hardy" and 
  109.             not "RELEASE_UPGRADE_NO_RECOMMENDS" in os.environ):
  110.             apt_pkg.Config.Set("APT::Install-Recommends","true")
  111.  
  112.     @property
  113.     def reqReinstallPkgs(self):
  114.         " return the packages not downloadable packages in reqreinst state "
  115.         reqreinst = set()
  116.         for pkg in self:
  117.             if (not pkg.candidateDownloadable and 
  118.                 (pkg._pkg.InstState == self.ReInstReq or
  119.                  pkg._pkg.InstState == self.HoldReInstReq)):
  120.                 reqreinst.add(pkg.name)
  121.         return reqreinst
  122.  
  123.     def fixReqReinst(self, view):
  124.         " check for reqreinst state and offer to fix it "
  125.         reqreinst = self.reqReinstallPkgs
  126.         if len(reqreinst) > 0:
  127.             header = ngettext("Remove package in bad state",
  128.                               "Remove packages in bad state", 
  129.                               len(reqreinst))
  130.             summary = ngettext("The package '%s' is in an inconsistent "
  131.                                "state and needs to be reinstalled, but "
  132.                                "no archive can be found for it. "
  133.                                "Do you want to remove this package "
  134.                                "now to continue?",
  135.                                "The packages '%s' are in an inconsistent "
  136.                                "state and need to be reinstalled, but "
  137.                                "no archives can be found for them. Do you "
  138.                                "want to remove these packages now to "
  139.                                "continue?",
  140.                                len(reqreinst)) % ", ".join(reqreinst)
  141.             if view.askYesNoQuestion(header, summary):
  142.                 self.releaseLock()
  143.                 cmd = ["dpkg","--remove","--force-remove-reinstreq"] + list(reqreinst)
  144.                 view.getTerminal().call(cmd)
  145.                 self.getLock()
  146.                 return True
  147.         return False
  148.  
  149.     # logging stuff
  150.     def _initAptLog(self):
  151.         " init logging, create log file"
  152.         logdir = self.config.getWithDefault("Files","LogDir",
  153.                                             "/var/log/dist-upgrade")
  154.         apt_pkg.Config.Set("Dir::Log",logdir)
  155.         apt_pkg.Config.Set("Dir::Log::Terminal","apt-term.log")
  156.         self.logfd = os.open(os.path.join(logdir,"apt.log"),
  157.                              os.O_RDWR|os.O_CREAT|os.O_APPEND|os.O_SYNC, 0644)
  158.         os.write(self.logfd, "Log time: %s\n" % datetime.datetime.now())
  159.         # turn on debugging in the cache
  160.         apt_pkg.Config.Set("Debug::pkgProblemResolver","true")
  161.         apt_pkg.Config.Set("Debug::pkgDepCache::AutoInstall","true")
  162.     def _startAptResolverLog(self):
  163.         if hasattr(self, "old_stdout"):
  164.             os.close(self.old_stdout)
  165.             os.close(self.old_stderr)
  166.         self.old_stdout = os.dup(1)
  167.         self.old_stderr = os.dup(2)
  168.         os.dup2(self.logfd, 1)
  169.         os.dup2(self.logfd, 2)
  170.     def _stopAptResolverLog(self):
  171.         os.fsync(1)
  172.         os.fsync(2)
  173.         os.dup2(self.old_stdout, 1)
  174.         os.dup2(self.old_stderr, 2)
  175.     # use this decorator instead of the _start/_stop stuff directly
  176.     # FIXME: this should probably be a decorator class where all
  177.     #        logging is moved into?
  178.     def withResolverLog(f):
  179.         " decorator to ensure that the apt output is logged "
  180.         def wrapper(*args, **kwargs):
  181.             args[0]._startAptResolverLog()
  182.             res = f(*args, **kwargs)
  183.             args[0]._stopAptResolverLog()
  184.             return res
  185.         return wrapper
  186.  
  187.     # properties
  188.     @property
  189.     def requiredDownload(self):
  190.         """ get the size of the packages that are required to download """
  191.         pm = apt_pkg.GetPackageManager(self._depcache)
  192.         fetcher = apt_pkg.GetAcquire()
  193.         pm.GetArchives(fetcher, self._list, self._records)
  194.         return fetcher.FetchNeeded
  195.     @property
  196.     def additionalRequiredSpace(self):
  197.         """ get the size of the additional required space on the fs """
  198.         return self._depcache.UsrSize
  199.     @property
  200.     def isBroken(self):
  201.         """ is the cache broken """
  202.         return self._depcache.BrokenCount > 0
  203.  
  204.     # methods
  205.     def lockListsDir(self):
  206.         name = apt_pkg.Config.FindDir("Dir::State::Lists") + "lock"
  207.         self._listsLock = apt_pkg.GetLock(name)
  208.         if self._listsLock < 0:
  209.             e = "Can not lock '%s' " % name
  210.             raise CacheExceptionLockingFailed, e
  211.     def unlockListsDir(self):
  212.         if self._listsLock > 0:
  213.             os.close(self._listsLock)
  214.             self._listsLock = -1
  215.     def update(self, fprogress=None):
  216.         """
  217.         our own update implementation is required because we keep the lists
  218.         dir lock
  219.         """
  220.         self.unlockListsDir()
  221.         res = apt.Cache.update(self, fprogress)
  222.         self.lockListsDir()
  223.         if res == False:
  224.             raise IOError("apt.cache.update() returned False, but did not raise exception?!?")
  225.  
  226.     def commit(self, fprogress, iprogress):
  227.         logging.info("cache.commit()")
  228.         if self.lock:
  229.             self.releaseLock()
  230.         apt.Cache.commit(self, fprogress, iprogress)
  231.  
  232.     def releaseLock(self, pkgSystemOnly=True):
  233.         if self.lock:
  234.             try:
  235.                 apt_pkg.PkgSystemUnLock()
  236.                 self.lock = False
  237.             except SystemError, e:
  238.                 logging.debug("failed to SystemUnLock() (%s) " % e)
  239.  
  240.     def getLock(self, pkgSystemOnly=True):
  241.         if not self.lock:
  242.             try:
  243.                 apt_pkg.PkgSystemLock()
  244.                 self.lock = True
  245.             except SystemError, e:
  246.                 logging.debug("failed to SystemLock() (%s) " % e)
  247.  
  248.     def downloadable(self, pkg, useCandidate=True):
  249.         " check if the given pkg can be downloaded "
  250.         if useCandidate:
  251.             ver = self._depcache.GetCandidateVer(pkg._pkg)
  252.         else:
  253.             ver = pkg._pkg.CurrentVer
  254.         if ver == None:
  255.             logging.warning("no version information for '%s' (useCandidate=%s)" % (pkg.name, useCandidate))
  256.             return False
  257.         return ver.Downloadable
  258.     
  259.     def fixBroken(self):
  260.         """ try to fix broken dependencies on the system, may throw
  261.             SystemError when it can't"""
  262.         return self._depcache.FixBroken()
  263.  
  264.     def create_snapshot(self):
  265.         """ create a snapshot of the current changes """
  266.         self.to_install = []
  267.         self.to_remove = []
  268.         for pkg in self.getChanges():
  269.             if pkg.markedInstall or pkg.markedUpgrade:
  270.                 self.to_install.append(pkg.name)
  271.             if pkg.markedDelete:
  272.                 self.to_remove.append(pkg.name)
  273.  
  274.     def clear(self):
  275.         self._depcache.Init()
  276.  
  277.     def restore_snapshot(self):
  278.         """ restore a snapshot """
  279.         actiongroup = apt_pkg.GetPkgActionGroup(self._depcache)
  280.         self.clear()
  281.         for name in self.to_remove:
  282.             pkg = self[name]
  283.             pkg.markDelete()
  284.         for name in self.to_install:
  285.             pkg = self[name]
  286.             pkg.markInstall(autoFix=False, autoInst=False)
  287.  
  288.     def needServerMode(self):
  289.         """ 
  290.         This checks if we run on a desktop or a server install.
  291.         
  292.         A server install has more freedoms, for a desktop install
  293.         we force a desktop meta package to be install on the upgrade.
  294.  
  295.         We look for a installed desktop meta pkg and for key 
  296.         dependencies, if none of those are installed we assume
  297.         server mode
  298.         """
  299.         #logging.debug("needServerMode() run")
  300.         # check for the MetaPkgs (e.g. ubuntu-desktop)
  301.         metapkgs = self.config.getlist("Distro","MetaPkgs")
  302.         for key in metapkgs:
  303.             # if it is installed we are done
  304.             if self.has_key(key) and self[key].isInstalled:
  305.                 logging.debug("needServerMode(): run in 'desktop' mode, (because of pkg '%s')" % key)
  306.                 return False
  307.             # if it is not installed, but its key depends are installed 
  308.             # we are done too (we auto-select the package later)
  309.             deps_found = True
  310.             for pkg in self.config.getlist(key,"KeyDependencies"):
  311.                 deps_found &= self.has_key(pkg) and self[pkg].isInstalled
  312.             if deps_found:
  313.                 logging.debug("needServerMode(): run in 'desktop' mode, (because of key deps for '%s')" % key)
  314.                 return False
  315.         logging.debug("needServerMode(): can not find a desktop meta package or key deps, running in server mode")
  316.         return True
  317.  
  318.     def sanityCheck(self, view):
  319.         """ check if the cache is ok and if the required metapkgs
  320.             are installed
  321.         """
  322.         if self.isBroken:
  323.             try:
  324.                 logging.debug("Have broken pkgs, trying to fix them")
  325.                 self.fixBroken()
  326.             except SystemError:
  327.                 view.error(_("Broken packages"),
  328.                                  _("Your system contains broken packages "
  329.                                    "that couldn't be fixed with this "
  330.                                    "software. "
  331.                                    "Please fix them first using synaptic or "
  332.                                    "apt-get before proceeding."))
  333.                 return False
  334.         return True
  335.  
  336.     def markInstall(self, pkg, reason=""):
  337.         logging.debug("Installing '%s' (%s)" % (pkg, reason))
  338.         if self.has_key(pkg):
  339.             self[pkg].markInstall()
  340.             if not (self[pkg].markedInstall or self[pkg].markedUpgrade):
  341.                 logging.error("Installing/upgrading '%s' failed" % pkg)
  342.                 #raise (SystemError, "Installing '%s' failed" % pkg)
  343.     def markUpgrade(self, pkg, reason=""):
  344.         logging.debug("Upgrading '%s' (%s)" % (pkg, reason))
  345.         if self.has_key(pkg) and self[pkg].isInstalled:
  346.             self[pkg].markUpgrade()
  347.             if not self[pkg].markedUpgrade:
  348.                 logging.error("Upgrading '%s' failed" % pkg)
  349.     def markRemove(self, pkg, reason=""):
  350.         logging.debug("Removing '%s' (%s)" % (pkg, reason))
  351.         if self.has_key(pkg):
  352.             self[pkg].markDelete()
  353.     def markPurge(self, pkg, reason=""):
  354.         logging.debug("Purging '%s' (%s)" % (pkg, reason))
  355.         if self.has_key(pkg):
  356.             self._depcache.MarkDelete(self[pkg]._pkg,True)
  357.  
  358.     def keepInstalledRule(self):
  359.         """ run after the dist-upgrade to ensure that certain
  360.             packages are kept installed """
  361.         def keepInstalled(self, pkgname, reason):
  362.             if (self.has_key(pkgname)
  363.                 and self[pkgname].isInstalled
  364.                 and self[pkgname].markedDelete):
  365.                 self.markInstall(pkgname, reason)
  366.                 
  367.         # first the global list
  368.         for pkgname in self.config.getlist("Distro","KeepInstalledPkgs"):
  369.             keepInstalled(self, pkgname, "Distro KeepInstalledPkgs rule")
  370.         # the the per-metapkg rules
  371.         for key in self.metapkgs:
  372.             if self.has_key(key) and (self[key].isInstalled or
  373.                                       self[key].markedInstall):
  374.                 for pkgname in self.config.getlist(key,"KeepInstalledPkgs"):
  375.                     keepInstalled(self, pkgname, "%s KeepInstalledPkgs rule" % key)
  376.  
  377.         # only enforce section if we have a network. Otherwise we run
  378.         # into CD upgrade issues for installed language packs etc
  379.         if self.config.get("Options","withNetwork") == "True":
  380.             logging.debug("Running KeepInstalledSection rules")
  381.             # now the KeepInstalledSection code
  382.             for section in self.config.getlist("Distro","KeepInstalledSection"):
  383.                 for pkg in self:
  384.                     if pkg.markedDelete and pkg.section == section:
  385.                         keepInstalled(self, pkg.name, "Distro KeepInstalledSection rule: %s" % section)
  386.             for key in self.metapkgs:
  387.                 if self.has_key(key) and (self[key].isInstalled or
  388.                                           self[key].markedInstall):
  389.                     for section in self.config.getlist(key,"KeepInstalledSection"):
  390.                         for pkg in self:
  391.                             if pkg.markedDelete and pkg.section == section:
  392.                                 keepInstalled(self, pkg.name, "%s KeepInstalledSection rule: %s" % (key, section))
  393.         
  394.  
  395.     def postUpgradeRule(self):
  396.         " run after the upgrade was done in the cache "
  397.         for (rule, action) in [("Install", self.markInstall),
  398.                                ("Upgrade", self.markUpgrade),
  399.                                ("Remove", self.markRemove),
  400.                                ("Purge", self.markPurge)]:
  401.             # first the global list
  402.             for pkg in self.config.getlist("Distro","PostUpgrade%s" % rule):
  403.                 action(pkg, "Distro PostUpgrade%s rule" % rule)
  404.             for key in self.metapkgs:
  405.                 if self.has_key(key) and (self[key].isInstalled or
  406.                                           self[key].markedInstall):
  407.                     for pkg in self.config.getlist(key,"PostUpgrade%s" % rule):
  408.                         action(pkg, "%s PostUpgrade%s rule" % (key, rule))
  409.         # run the quirks handlers
  410.         if not self.partialUpgrade:
  411.             self.quirks.run("PostDistUpgradeCache")
  412.  
  413.     def identifyObsoleteKernels(self):
  414.         # we have a funny policy that we remove security updates
  415.         # for the kernel from the archive again when a new ABI
  416.         # version hits the archive. this means that we have
  417.         # e.g. 
  418.         # linux-image-2.6.24-15-generic 
  419.         # is obsolete when 
  420.         # linux-image-2.6.24-19-generic
  421.         # is available
  422.         # ...
  423.         # This code tries to identify the kernels that can be removed
  424.         logging.debug("identifyObsoleteKernels()")
  425.         obsolete_kernels = set()
  426.         version = self.config.get("KernelRemoval","Version")
  427.         basenames = self.config.getlist("KernelRemoval","BaseNames")
  428.         types = self.config.getlist("KernelRemoval","Types")
  429.         for pkg in self:
  430.             for base in basenames:
  431.                 basename = "%s-%s-" % (base,version)
  432.                 for type in types:
  433.                     if (pkg.name.startswith(basename) and 
  434.                         pkg.name.endswith(type) and
  435.                         pkg.isInstalled):
  436.                         if (pkg.name == "%s-%s" % (base,self.uname)):
  437.                             logging.debug("skipping running kernel %s" % pkg.name)
  438.                             continue
  439.                         logging.debug("removing obsolete kernel '%s'" % pkg.name)
  440.                         obsolete_kernels.add(pkg.name)
  441.         logging.debug("identifyObsoleteKernels found '%s'" % obsolete_kernels)
  442.         return obsolete_kernels
  443.  
  444.     def checkForNvidia(self):
  445.         """ 
  446.         this checks for nvidia hardware and checks what driver is needed
  447.         """
  448.         logging.debug("nvidiaUpdate()")
  449.         # if the free drivers would give us a equally hard time, we would
  450.         # never be able to release
  451.         try:
  452.             from NvidiaDetector.nvidiadetector import NvidiaDetection
  453.         except ImportError, e:
  454.             logging.error("NvidiaDetector can not be imported %s" % e)
  455.             return False
  456.         try:
  457.             # get new detection module and use the modalises files
  458.             # from within the release-upgrader
  459.             nv = NvidiaDetection(datadir="./modaliases/")
  460.             #nv = NvidiaDetection()
  461.             # check if a binary driver is installed now
  462.             for oldDriver in nv.oldPackages:
  463.                 if self.has_key(oldDriver) and self[oldDriver].isInstalled:
  464.                     self.markRemove(oldDriver, "old nvidia driver")
  465.                     break
  466.             else:
  467.                 logging.info("no old nvidia driver installed, installing no new")
  468.                 return False
  469.             # check which one to use
  470.             driver = nv.selectDriver()
  471.             logging.debug("nv.selectDriver() returned '%s'" % driver)
  472.             if (self.has_key(driver) and not
  473.                 (self[driver].markedInstall or self[driver].markedUpgrade)):
  474.                 self[driver].markInstall()
  475.                 logging.info("installing %s as suggested by NvidiaDetector" % driver)
  476.                 return True
  477.         except Exception, e:
  478.             logging.error("NvidiaDetection returned a error: %s" % e)
  479.         return False
  480.  
  481.     def checkForKernel(self):
  482.         """ check for the running kernel and try to ensure that we have
  483.             an updated version
  484.         """
  485.         logging.debug("Kernel uname: '%s' " % self.uname)
  486.         try:
  487.             (version, build, flavour) = self.uname.split("-")
  488.         except Exception, e:
  489.             logging.warning("Can't parse kernel uname: '%s' (self compiled?)" % e)
  490.             return False
  491.         # now check if we have a SMP system
  492.         dmesg = Popen(["dmesg"],stdout=PIPE).communicate()[0]
  493.         if "WARNING: NR_CPUS limit" in dmesg:
  494.             logging.debug("UP kernel on SMP system!?!")
  495.             flavour = "generic"
  496.         kernel = "linux-image-%s" % flavour
  497.         if not self.has_key(kernel):
  498.             logging.warning("No kernel: '%s'" % kernel)
  499.             return False
  500.         if not (self[kernel].isInstalled or self[kernel].markedInstall):
  501.             logging.debug("Selecting new kernel '%s'" % kernel)
  502.             self[kernel].markInstall()
  503.         return True
  504.  
  505.     def checkPriority(self):
  506.         # tuple of priorities we require to be installed 
  507.         need = ('required', )
  508.         # stuff that its ok not to have
  509.         removeEssentialOk = self.config.getlist("Distro","RemoveEssentialOk")
  510.         # check now
  511.         for pkg in self:
  512.             # WORKADOUND bug on the CD/python-apt #253255
  513.             ver = pkg._pcache._depcache.GetCandidateVer(pkg._pkg)
  514.             if ver and ver.Priority == 0:
  515.                 logging.error("Package %s has no priority set" % pkg.name)
  516.                 continue
  517.             if (pkg.candidateDownloadable and
  518.                 not (pkg.isInstalled or pkg.markedInstall) and
  519.                 not pkg.name in removeEssentialOk and
  520.                 pkg.priority in need):
  521.                 self.markInstall(pkg.name, "priority in required set '%s' but not scheduled for install" % need)
  522.  
  523.     # FIXME: make this a decorator (just like the withResolverLog())
  524.     def updateGUI(self, view, lock):
  525.         while lock.locked():
  526.             view.processEvents()
  527.             time.sleep(0.01)
  528.  
  529.     @withResolverLog
  530.     def distUpgrade(self, view, serverMode, partialUpgrade):
  531.         # keep the GUI alive
  532.         lock = threading.Lock()
  533.         lock.acquire()
  534.         t = threading.Thread(target=self.updateGUI, args=(self.view, lock,))
  535.         t.start()
  536.         try:
  537.             # upgrade (and make sure this way that the cache is ok)
  538.             self.upgrade(True)
  539.  
  540.             # check that everything in priority required is installed
  541.             self.checkPriority()
  542.  
  543.             # see if our KeepInstalled rules are honored
  544.             self.keepInstalledRule()
  545.  
  546.             # check if we got a new kernel
  547.             self.checkForKernel()
  548.  
  549.             # check for nvidia stuff
  550.             self.checkForNvidia()
  551.  
  552.             # and if we have some special rules
  553.             self.postUpgradeRule()
  554.  
  555.             # install missing meta-packages (if not in server upgrade mode)
  556.             if not serverMode:
  557.                 # if this fails, a system error is raised
  558.                 self._installMetaPkgs(view)
  559.  
  560.             # see if it all makes sense, if not this function raises 
  561.             self._verifyChanges()
  562.  
  563.         except SystemError, e:
  564.             # this should go into a finally: line, see below for the 
  565.             # rationale why it doesn't 
  566.             lock.release()
  567.             t.join()
  568.             # FIXME: change the text to something more useful
  569.             details =  _("An unresolvable problem occurred while "
  570.                          "calculating the upgrade:\n%s\n\n "
  571.                          "This can be caused by:\n"
  572.                          " * Upgrading to a pre-release version of Ubuntu\n"
  573.                          " * Running the current pre-release version of Ubuntu\n"
  574.                          " * Unofficial software packages not provided by Ubuntu\n"
  575.                          "\n" % e)
  576.             # we never have partialUpgrades (including removes) on a stable system
  577.             # with only ubuntu sources so we do not recommend reporting a bug
  578.             if partialUpgrade:
  579.                 details += _("This is most likely a transient problem, "
  580.                              "please try again later.")
  581.             else:
  582.                 details += _("If none of this applies, then please report this bug against "
  583.                              "the 'update-manager' package and include the files in "
  584.                              "/var/log/dist-upgrade/ in the bug report.")
  585.             # make the error text available again on stdout for the 
  586.             # text frontend
  587.             self._stopAptResolverLog()
  588.             view.error(_("Could not calculate the upgrade"), details)
  589.             # start the resolver log again because this is run with
  590.             # the withResolverLog decorator
  591.             self._startAptResolverLog()            
  592.             logging.error("Dist-upgrade failed: '%s'", e)
  593.             return False
  594.         # would be nice to be able to use finally: here, but we need
  595.         # to run on python2.4 too 
  596.         #finally:
  597.         # wait for the gui-update thread to exit
  598.         lock.release()
  599.         t.join()
  600.         
  601.         # check the trust of the packages that are going to change
  602.         untrusted = []
  603.         for pkg in self.getChanges():
  604.             if pkg.markedDelete:
  605.                 continue
  606.             # special case because of a bug in pkg.candidateOrigin
  607.             if pkg.markedDowngrade:
  608.                 for ver in pkg._pkg.VersionList:
  609.                     # version is lower than installed one
  610.                     if apt_pkg.VersionCompare(ver.VerStr, pkg.installedVersion) < 0:
  611.                         for (verFileIter,index) in ver.FileList:
  612.                             indexfile = pkg._list.FindIndex(verFileIter)
  613.                             if indexfile and not indexfile.IsTrusted:
  614.                                 untrusted.append(pkg.name)
  615.                                 break
  616.                 continue
  617.             origins = pkg.candidateOrigin
  618.             trusted = False
  619.             for origin in origins:
  620.                 #print origin
  621.                 trusted |= origin.trusted
  622.             if not trusted:
  623.                 untrusted.append(pkg.name)
  624.         # check if the user overwrote the unauthenticated warning
  625.         try:
  626.             b = self.config.getboolean("Distro","AllowUnauthenticated")
  627.             if b:
  628.                 logging.warning("AllowUnauthenticated set!")
  629.                 return True
  630.         except ConfigParser.NoOptionError, e:
  631.             pass
  632.         if len(untrusted) > 0:
  633.             untrusted.sort()
  634.             logging.error("Unauthenticated packages found: '%s'" % \
  635.                           " ".join(untrusted))
  636.             # FIXME: maybe ask a question here? instead of failing?
  637.             self._stopAptResolverLog()
  638.             view.error(_("Error authenticating some packages"),
  639.                        _("It was not possible to authenticate some "
  640.                          "packages. This may be a transient network problem. "
  641.                          "You may want to try again later. See below for a "
  642.                          "list of unauthenticated packages."),
  643.                        "\n".join(untrusted))
  644.             # start the resolver log again because this is run with
  645.             # the withResolverLog decorator
  646.             self._startAptResolverLog()            
  647.             return False
  648.         return True
  649.  
  650.     def _verifyChanges(self):
  651.         """ this function tests if the current changes don't violate
  652.             our constrains (blacklisted removals etc)
  653.         """
  654.         removeEssentialOk = self.config.getlist("Distro","RemoveEssentialOk")
  655.         # check changes
  656.         for pkg in self.getChanges():
  657.             if pkg.markedDelete and self._inRemovalBlacklist(pkg.name):
  658.                 logging.debug("The package '%s' is marked for removal but it's in the removal blacklist", pkg.name)
  659.                 raise SystemError, _("The package '%s' is marked for removal but it is in the removal blacklist.") % pkg.name
  660.             if pkg.markedDelete and (pkg._pkg.Essential == True and
  661.                                      not pkg.name in removeEssentialOk):
  662.                 logging.debug("The package '%s' is marked for removal but it's a ESSENTIAL package", pkg.name)
  663.                 raise SystemError, _("The essential package '%s' is marked for removal.") % pkg.name
  664.         # check bad-versions blacklist
  665.         badVersions = self.config.getlist("Distro","BadVersions")
  666.         for bv in badVersions:
  667.             (pkgname, ver) = bv.split("_")
  668.             if (self.has_key(pkgname) and
  669.                 self[pkgname].candidateVersion == ver and
  670.                 (self[pkgname].markedInstall or
  671.                  self[pkgname].markedUpgrade)):
  672.                 raise SystemError, "Trying to install blacklisted version '%s'" % bv
  673.         return True
  674.  
  675.     @property
  676.     def installedTasks(self):
  677.         tasks = {}
  678.         installed_tasks = set()
  679.         for pkg in self:
  680.             if not pkg._lookupRecord():
  681.                 logging.debug("no PkgRecord found for '%s', skipping " % pkg.name)
  682.                 continue
  683.             for line in pkg._pcache._records.Record.split("\n"):
  684.                 if line.startswith("Task:"):
  685.                     for task in (line[len("Task:"):]).split(","):
  686.                         task = task.strip()
  687.                         if not tasks.has_key(task):
  688.                             tasks[task] = set()
  689.                         tasks[task].add(pkg.name)
  690.         for task in tasks:
  691.             installed = True
  692.             for pkgname in tasks[task]:
  693.                 if not self[pkgname].isInstalled:
  694.                     installed = False
  695.                     break
  696.             if installed:
  697.                 installed_tasks.add(task)
  698.         return installed_tasks
  699.             
  700.     def installTasks(self, tasks):
  701.         logging.debug("running installTasks")
  702.         for pkg in self:
  703.             if pkg.markedInstall or pkg.isInstalled:
  704.                 continue
  705.             pkg._lookupRecord()
  706.             if not (hasattr(pkg._pcache._records,"Record") and pkg._pcache._records.Record):
  707.                 logging.warning("can not find Record for '%s'" % pkg.name)
  708.                 continue
  709.             for line in pkg._pcache._records.Record.split("\n"):
  710.                 if line.startswith("Task:"):
  711.                     for task in (line[len("Task:"):]).split(","):
  712.                         task = task.strip()
  713.                         if task in tasks:
  714.                             pkg.markInstall()
  715.         return True
  716.     
  717.     def _installMetaPkgs(self, view):
  718.  
  719.         def metaPkgInstalled():
  720.             """ 
  721.             internal helper that checks if at least one meta-pkg is 
  722.             installed or marked install
  723.             """
  724.             for key in metapkgs:
  725.                 if self.has_key(key):
  726.                     pkg = self[key]
  727.                     if pkg.isInstalled and pkg.markedDelete:
  728.                         logging.debug("metapkg '%s' installed but markedDelete" % pkg.name)
  729.                     if ((pkg.isInstalled and not pkg.markedDelete) 
  730.                         or self[key].markedInstall):
  731.                         return True
  732.             return False
  733.  
  734.         # now check for ubuntu-desktop, kubuntu-desktop, edubuntu-desktop
  735.         metapkgs = self.config.getlist("Distro","MetaPkgs")
  736.  
  737.         # we never go without ubuntu-base
  738.         for pkg in self.config.getlist("Distro","BaseMetaPkgs"):
  739.             self[pkg].markInstall()
  740.  
  741.         # every meta-pkg that is installed currently, will be marked
  742.         # install (that result in a upgrade and removes a markDelete)
  743.         for key in metapkgs:
  744.             try:
  745.                 if self.has_key(key) and self[key].isInstalled:
  746.                     logging.debug("Marking '%s' for upgrade" % key)
  747.                     self[key].markUpgrade()
  748.             except SystemError, e:
  749.                 logging.debug("Can't mark '%s' for upgrade (%s)" % (key,e))
  750.                 raise SystemError, _("Can not mark '%s' for upgrade") % key
  751.         # check if we have a meta-pkg, if not, try to guess which one to pick
  752.         if not metaPkgInstalled():
  753.             logging.debug("none of the '%s' meta-pkgs installed" % metapkgs)
  754.             for key in metapkgs:
  755.                 deps_found = True
  756.                 for pkg in self.config.getlist(key,"KeyDependencies"):
  757.                     deps_found &= self.has_key(pkg) and self[pkg].isInstalled
  758.                 if deps_found:
  759.                     logging.debug("guessing '%s' as missing meta-pkg" % key)
  760.                     try:
  761.                         self[key].markInstall()
  762.                     except (SystemError, KeyError), e:
  763.                         logging.error("failed to mark '%s' for install (%s)" % (key,e))
  764.                         view.error(_("Can't install '%s'" % key),
  765.                                    _("It was impossible to install a "
  766.                                      "required package. Please report "
  767.                                      "this as a bug. "))
  768.                         return False
  769.                     logging.debug("markedInstall: '%s' -> '%s'" % (key, self[key].markedInstall))
  770.         # check if we actually found one
  771.         if not metaPkgInstalled():
  772.             # FIXME: provide a list
  773.             view.error(_("Can't guess meta-package"),
  774.                        _("Your system does not contain a "
  775.                          "ubuntu-desktop, kubuntu-desktop, xubuntu-desktop or "
  776.                          "edubuntu-desktop package and it was not "
  777.                          "possible to detect which version of "
  778.                         "Ubuntu you are running.\n "
  779.                          "Please install one of the packages "
  780.                          "above first using synaptic or "
  781.                          "apt-get before proceeding."))
  782.             return False
  783.         return True
  784.  
  785.     def _inRemovalBlacklist(self, pkgname):
  786.         for expr in self.removal_blacklist:
  787.             if re.compile(expr).match(pkgname):
  788.                 return True
  789.         return False
  790.  
  791.     @withResolverLog
  792.     def tryMarkObsoleteForRemoval(self, pkgname, remove_candidates, foreign_pkgs):
  793.         #logging.debug("tryMarkObsoleteForRemoval(): %s" % pkgname)
  794.         # sanity check, first see if it looks like a running kernel pkg
  795.         if pkgname.endswith(self.uname):
  796.             logging.debug("skipping running kernel pkg '%s'" % pkgname)
  797.             return False
  798.         if self._inRemovalBlacklist(pkgname):
  799.             logging.debug("skipping '%s' (in removalBlacklist)" % pkgname)
  800.             return False
  801.         # ensure we honor KeepInstalledSection here as well
  802.         for section in self.config.getlist("Distro","KeepInstalledSection"):
  803.             if self.has_key(pkgname) and self[pkgname].section == section:
  804.                 logging.debug("skipping '%s' (in KeepInstalledSection)" % pkgname)
  805.                 return False
  806.         # if we don't have the package anyway, we are fine (this can
  807.         # happen when forced_obsoletes are specified in the config file)
  808.         if not self.has_key(pkgname):
  809.             #logging.debug("package '%s' not in cache" % pkgname)
  810.             return True
  811.         # check if we want to purge 
  812.         try:
  813.             purge = self.config.getboolean("Distro","PurgeObsoletes")
  814.         except ConfigParser.NoOptionError, e:
  815.             purge = False
  816.  
  817.         # this is a delete candidate, only actually delete,
  818.         # if it dosn't remove other packages depending on it
  819.         # that are not obsolete as well
  820.         actiongroup = apt_pkg.GetPkgActionGroup(self._depcache)
  821.         self.create_snapshot()
  822.         try:
  823.             self[pkgname].markDelete(purge=purge)
  824.             self.view.processEvents()
  825.             #logging.debug("marking '%s' for removal" % pkgname)
  826.             for pkg in self.getChanges():
  827.                 if (pkg.name not in remove_candidates or 
  828.                       pkg.name in foreign_pkgs or 
  829.                       self._inRemovalBlacklist(pkg.name)):
  830.                     logging.debug("package '%s' has unwanted removals, skipping" % pkgname)
  831.                     self.restore_snapshot()
  832.                     return False
  833.         except (SystemError,KeyError),e:
  834.             logging.warning("_tryMarkObsoleteForRemoval failed for '%s' (%s: %s)" % (pkgname, repr(e), e))
  835.             self.restore_snapshot()
  836.             return False
  837.         return True
  838.     
  839.     def _getObsoletesPkgs(self):
  840.         " get all package names that are not downloadable "
  841.         obsolete_pkgs =set()        
  842.         for pkg in self:
  843.             if pkg.isInstalled: 
  844.                 # check if any version is downloadable. we need to check
  845.                 # for older ones too, because there might be
  846.                 # cases where e.g. firefox in gutsy-updates is newer
  847.                 # than hardy
  848.                 if not self.anyVersionDownloadable(pkg):
  849.                     obsolete_pkgs.add(pkg.name)
  850.         return obsolete_pkgs
  851.  
  852.     def anyVersionDownloadable(self, pkg):
  853.         " helper that checks if any of the version of pkg is downloadable "
  854.         for ver in pkg._pkg.VersionList:
  855.             if ver.Downloadable:
  856.                 return True
  857.         return False
  858.  
  859.     def _getUnusedDependencies(self):
  860.         " get all package names that are not downloadable "
  861.         unused_dependencies =set()        
  862.         for pkg in self:
  863.             if pkg.isInstalled and self._depcache.IsGarbage(pkg._pkg):
  864.                 unused_dependencies.add(pkg.name)
  865.         return unused_dependencies
  866.  
  867.     def _getForeignPkgs(self, allowed_origin, fromDist, toDist):
  868.         """ get all packages that are installed from a foreign repo
  869.             (and are actually downloadable)
  870.         """
  871.         foreign_pkgs=set()        
  872.         for pkg in self:
  873.             if pkg.isInstalled and self.downloadable(pkg):
  874.                 # assume it is foreign and see if it is from the 
  875.                 # official archive
  876.                 foreign=True
  877.                 for origin in pkg.candidateOrigin:
  878.                     # FIXME: use some better metric here
  879.                     if fromDist in origin.archive and \
  880.                            origin.origin == allowed_origin:
  881.                         foreign = False
  882.                     if toDist in origin.archive and \
  883.                            origin.origin == allowed_origin:
  884.                         foreign = False
  885.                 if foreign:
  886.                     foreign_pkgs.add(pkg.name)
  887.         return foreign_pkgs
  888.  
  889.     def checkFreeSpace(self):
  890.         """
  891.         this checks if we have enough free space on /var, /boot and /usr
  892.         with the given cache 
  893.  
  894.         Note: this can not be fully accurate if there are multiple 
  895.               mountpoints for /usr, /var, /boot
  896.         """
  897.  
  898.         class FreeSpace(object):
  899.             " helper class that represents the free space on each mounted fs "
  900.             def __init__(self, initialFree):
  901.                 self.free = initialFree
  902.                 self.need = 0
  903.  
  904.         def make_fs_id(d):
  905.             """ return 'id' of a directory so that directories on the
  906.                 same filesystem get the same id (simply the mount_point)
  907.             """
  908.             for mount_point in mounted:
  909.                 if d.startswith(mount_point):
  910.                     return mount_point
  911.             return "/"
  912.  
  913.         # this is all a bit complicated
  914.         # 1) check what is mounted (in mounted)
  915.         # 2) create FreeSpace objects for the dirs we are interested in
  916.         #    (mnt_map)
  917.         # 3) use the  mnt_map to check if we have enough free space and
  918.         #    if not tell the user how much is missing
  919.         mounted = []
  920.         mnt_map = {}
  921.         fs_free = {}
  922.         for line in open("/proc/mounts"):
  923.             try:
  924.                 (what, where, fs, options, a, b) = line.split()
  925.             except ValueError, e:
  926.                 logging.debug("line '%s' in /proc/mounts not understood (%s)" % (line, e))
  927.                 continue
  928.             if not where in mounted:
  929.                 mounted.append(where)
  930.         # make sure mounted is sorted by longest path
  931.         mounted.sort(cmp=lambda a,b: cmp(len(a),len(b)), reverse=True)
  932.         archivedir = apt_pkg.Config.FindDir("Dir::Cache::archives")
  933.         aufs_rw_dir = "/tmp/"
  934.         if (hasattr(self, "config") and
  935.             self.config.getWithDefault("Aufs","Enabled", False)):
  936.             aufs_rw_dir = self.config.get("Aufs","RWDir")
  937.             if not os.path.exists(aufs_rw_dir):
  938.                 os.makedirs(aufs_rw_dir)
  939.         logging.debug("cache aufs_rw_dir: %s" % aufs_rw_dir)
  940.         for d in ["/","/usr","/var","/boot", archivedir, aufs_rw_dir, "/home"]:
  941.             d = os.path.realpath(d)
  942.             fs_id = make_fs_id(d)
  943.             st = os.statvfs(d)
  944.             free = st[statvfs.F_BAVAIL]*st[statvfs.F_FRSIZE]
  945.             if fs_id in mnt_map:
  946.                 logging.debug("Dir %s mounted on %s" % (d,mnt_map[fs_id]))
  947.                 fs_free[d] = fs_free[mnt_map[fs_id]]
  948.             else:
  949.                 logging.debug("Free space on %s: %s" % (d,free))
  950.                 mnt_map[fs_id] = d
  951.                 fs_free[d] = FreeSpace(free)
  952.         del mnt_map
  953.         logging.debug("fs_free contains: '%s'" % fs_free)
  954.  
  955.         # now calculate the space that is required on /boot
  956.         # we do this by checking how many linux-image-$ver packages
  957.         # are installed or going to be installed
  958.         space_in_boot = 0
  959.         for pkg in self:
  960.             # we match against everything that looks like a kernel
  961.             # and add space check to filter out metapackages
  962.             if re.match("^linux-(image|image-debug)-[0-9.]*-.*", pkg.name):
  963.                 if pkg.markedInstall:
  964.                     logging.debug("%s (new-install) added with %s to boot space" % (pkg.name, KERNEL_INITRD_SIZE))
  965.                     space_in_boot += KERNEL_INITRD_SIZE
  966.                 # mvo: jaunty does not create .bak files anymore
  967.                 #elif (pkg.markedUpgrade or pkg.isInstalled):
  968.                 #    logging.debug("%s (upgrade|installed) added with %s to boot space" % (pkg.name, KERNEL_INITRD_SIZE))
  969.                 #    space_in_boot += KERNEL_INITRD_SIZE # creates .bak
  970.  
  971.         # we check for various sizes:
  972.         # archivedir is were we download the debs
  973.         # /usr is assumed to get *all* of the install space (incorrect,
  974.         #      but as good as we can do currently + safety buffer
  975.         # /     has a small safety buffer as well
  976.         required_for_aufs = 0.0
  977.         if (hasattr(self, "config") and
  978.             self.config.getWithDefault("Aufs","Enabled", False)):
  979.             logging.debug("taking aufs overlay into space calculation")
  980.             aufs_rw_dir = self.config.get("Aufs","RWDir")
  981.             # if we use the aufs rw overlay all the space is consumed
  982.             # the overlay dir
  983.             for pkg in self:
  984.                 if pkg.markedUpgrade or pkg.markedInstall:
  985.                     required_for_aufs += self._depcache.GetCandidateVer(pkg._pkg).Size
  986.                     
  987.         # sum up space requirements
  988.         for (dir, size) in [(archivedir, self.requiredDownload),
  989.                             # plus 50M safety buffer in /usr
  990.                             ("/usr", self.additionalRequiredSpace),
  991.                             ("/usr", 50*1024*1024),
  992.                             ("/boot", space_in_boot), 
  993.                             ("/", 10*1024*1024),     # small safety buffer /
  994.                             (aufs_rw_dir, required_for_aufs),
  995.                            ]:
  996.             dir = os.path.realpath(dir)
  997.             logging.debug("dir '%s' needs '%s' of '%s' (%f)" % (dir, size, fs_free[dir], fs_free[dir].free))
  998.             fs_free[dir].free -= size
  999.             fs_free[dir].need += size
  1000.  
  1001.         # check for space required violations
  1002.         required_list = {}
  1003.         for dir in fs_free:
  1004.             if fs_free[dir].free < 0:
  1005.                 free_at_least = apt_pkg.SizeToStr(float(abs(fs_free[dir].free)+1))
  1006.                 # make_fs_id ensures we only get stuff on the same
  1007.                 # mountpoint, so we report the requirements only once
  1008.                 # per mountpoint
  1009.                 required_list[make_fs_id(dir)] = FreeSpaceRequired(apt_pkg.SizeToStr(fs_free[dir].need), make_fs_id(dir), free_at_least)
  1010.         # raise exception if free space check fails
  1011.         if len(required_list) > 0:
  1012.             logging.error("Not enough free space: %s" % [str(i) for i in required_list])
  1013.             raise NotEnoughFreeSpaceError(required_list.values())
  1014.         return True
  1015.  
  1016.  
  1017.  
  1018. if __name__ == "__main__":
  1019.     import DistUpgradeConfigParser
  1020.         import DistUpgradeView
  1021.         print "foo"
  1022.     c = MyCache(DistUpgradeConfigParser.DistUpgradeConfig("."),
  1023.                     DistUpgradeView.DistUpgradeView(), None)
  1024.         #c.checkForNvidia()
  1025.         #print c._identifyObsoleteKernels()
  1026.         print c.checkFreeSpace()
  1027.         sys.exit()
  1028.     c.clear()
  1029.         c.create_snapshot()
  1030.         c.installedTasks
  1031.         c.installTasks(["ubuntu-desktop"])
  1032.         print c.getChanges()
  1033.         c.restore_snapshot()
  1034.